/******************************************************************************
 *                                                                            *
 *               (c) 2008-2009 HutLab. All rights reserved.                   *
 *                        Eduardo Henrique Leite                              *
 *                                                                            *
 ******************************************************************************

 History:
 Created 03/01/2009

 */

package hutlab.sudoku.engine.ui;

import hutlab.sudoku.engine.*;
import hutlab.sudoku.engine.base.*;
import hutlab.sudoku.engine.math.*;
import javax.microedition.lcdui.*;

/**
 * Sudoku Board display Class.
 */
public class SudokuBoard extends GameObject{

    // ----- CONSTATNTS -----

    private final int NUM_CELLS = 81;
    private final int NUM_COLS = 9;

    // ----- FIELDS -----

    private SudokuBoardCell[] mCells;
    private SudokuEngine mEngine;
    private ColourRGB mBorderColour;
    private ColourRGB mCellColour;
    private ColourRGB mGeneratedCellColour;
    private ColourRGB mSelectedCellColour;
    private ColourRGB mConflictedCellColour;
    private ColourRGB mSuggestedCellColour;
    private ColourRGB mFontColour;
    private Display mDisplay;
    private Font mFont;
    private int mCellSize = 10;
    private int mSelectedCellIndex;
    private int mSuggestedCellIndex;
    private int mMode;

    // ----- GETTERS -----

    /**
     * Returns the cell array.
     */
    public SudokuBoardCell[] getCells(){
        return mCells;
    }

    public SudokuEngine getEngine(){
        return mEngine;
    }

    /**
     * Returns the cell size.
     */
    public int getCellSize(){
        return mCellSize;
    }

    /**
     * Returns the cell's border colour.
     */
    public ColourRGB getBorderColour(){
        return mBorderColour;
    }

    /**
     * Returns the cell colour.
     */
    public ColourRGB getCellColour(){
        return mCellColour;
    }

    /**
     * Returns the generated cell colour.
     */
    public ColourRGB getGeneratedCellColour(){
        return mGeneratedCellColour;
    }

    /**
     * Returns the selected cell colour.
     */
    public ColourRGB getSelectedCellColour(){
        return mSelectedCellColour;
    }

    /**
     * Returns the conflicted cell colour.
     */
    public ColourRGB getConflictedCellColour(){
        return mConflictedCellColour;
    }

    public ColourRGB getSuggestedCellColour(){
        return mSuggestedCellColour;
    }

    /**
     * Returns the font colour.
     */
    public ColourRGB getFontColour(){
        return mFontColour;
    }

    /**
     * Returns the display object.
     */
    public Display getDisplay(){
        return mDisplay;
    }

    /**
     * Returns the font.
     */
    public Font getFont(){
        return mFont;
    }

    /**
     * Returns the selected cell index.
     */
    public int getSelectedCellIndex(){
        return mSelectedCellIndex;
    }

    public int getSuggestedCellIndex(){
        return mSuggestedCellIndex;
    }

    /**
     * Returns the board mode.
     */
    public int getMode(){
        return mMode;
    }

    /**
     * Returns the number of generated cells.
     */
    public int getGeneratedNumber(){
        int generatedCount = 0;
        for (int i=0;i<NUM_CELLS;i++){
            if (this.mCells[i].getGenerated()){
                generatedCount++;
            }
        }
        return generatedCount;
    }

    // ----- SETTERS -----

    /**
     * Sets the cell size.
     */
    public void setCellSize(int cellSize){
        mCellSize = cellSize;
    }

    public void setEngine(SudokuEngine engine){
        mEngine = engine;
    }

    /**
     * Sets the border colour.
     */
    public void setBorderColour(ColourRGB borderColour){
        mBorderColour = borderColour;
    }

    /**
     * Sets the cell colour.
     */
    public void setCellColour(ColourRGB cellColour){
        mCellColour = cellColour;
    }

    /**
     * Sets the generated cell colour.
     */
    public void setGeneratedCellColour(ColourRGB gereratedCellColour){
        mGeneratedCellColour = gereratedCellColour;
    }

    /**
     * Sets the selected cell colour.
     */
    public void setSelectedCellColour(ColourRGB selectedCellColour){
        mSelectedCellColour = selectedCellColour;
    }

    /**
     * Sets the conflicted cell colour.
     */
    public void setConflictedCellColour(ColourRGB conflictedCellColour){
        mConflictedCellColour = conflictedCellColour;
    }

    public void setSuggestedCellColour(ColourRGB suggestedCellColour){
        mSuggestedCellColour = suggestedCellColour;
    }

    /**
     * Sets the font colour.
     */
    public void setFontColour(ColourRGB fontColour){
        mFontColour = fontColour;
    }

    /**
     * Sets the Display object.
     */
    public void setDisplay(Display display){
        mDisplay = display;
    }

    /**
     * Sets the font.
     */
    public void setFont(Font font){
        mFont = font;
    }

    /**
     * Sets the selected cell index.
     */
    public void setSelectedCellIndex(int selectedCellIndex){
        this.mCells[this.mSelectedCellIndex].setSelected(false);
        this.mCells[selectedCellIndex].setSelected(true);
        this.mSelectedCellIndex = selectedCellIndex;
    }

    public void setSuggestedCellIndex(int suggestedCellIndex){
        this.mCells[this.mSuggestedCellIndex].setSuggested(false);
        this.mSuggestedCellIndex = suggestedCellIndex;

        if (suggestedCellIndex >= 0){
            this.mCells[suggestedCellIndex].setSuggested(true);
        }
    }

    /**
     * Sets the board mode.
     */
    public void setMode(int mode){
        mMode = mode;
    }

    // ----- CONSTRUCTOR -----

    /**
     * Creates a new SudokuBoard object.
     */
    public SudokuBoard(Display display, int mode){
        this.mCells = new SudokuBoardCell[NUM_CELLS];
        this.mMode = mode;
        this.mDisplay = display;
        this.clear();
        this.mSelectedCellIndex = 0;
    }

    // ----- PUBLIC -----

    /**
     * Clears the board.
     */
    public void clear(){
        for (int i=0; i<NUM_CELLS; i++){
            this.mCells[i] = new SudokuBoardCell();
        }
    }

    /**
     * GameObject Render method override.
     */
    public void render(Graphics graphics) {
        int x = getX();
        int y = getY();

        for (int i = 0; i< NUM_CELLS; i++){
            renderCell(graphics, i, x, y);

            if ((i + 1) % NUM_COLS == 0){
                y += this.mCellSize;
                x = getX();
            }
            else {
                x += this.mCellSize;
            }
        }
    }

    /**
     * GameObject KeyCommand method override.
     */
    public int keyCommand(int keyCode) {
        switch(keyCode){
            case SUDOKU_KEY_UP:
                moveUp();
                break;
            case SUDOKU_KEY_DOWN:
                moveDown();
                break;
            case SUDOKU_KEY_LEFT:
                moveLeft();
                break;
            case SUDOKU_KEY_RIGHT:
                moveRight();
                break;
            case SUDOKU_KEY_POUND:
                clearValue();
        }

        if (keyCode >= SUDOKU_KEY_1 && keyCode <= SUDOKU_KEY_9){
            changeValue((char) keyCode);
        }

        return 0;
    }

    /**
     * Checks for conflicts on the board.
     */
    public boolean checkConflicts(){
        for (int i=0; i<NUM_CELLS;i++){
            setConflict(i, false);
        }

        boolean conflicts = false;

        for (int i=0; i<NUM_CELLS;i++){
            conflicts |= hasConflicts(i);
        }

        return conflicts;
    }

    // ----- PRIVATE -----

    /**
     * Checks if the board has been cleared.
     */
    private boolean cleared(){
        for (int i=0; i<NUM_CELLS;i++){
            if (this.mCells[i].getValue() == 0){
                return false;
            }
        }
        
        return true;
    }

    /**
     * Checks for conflicts in a cell.
     */
    private boolean hasConflicts(int index){
        SudokuBoardCell compareCell = this.mCells[index];

        boolean hasConflicts = false;
        boolean conflictedCell = false;

        for (int i = 0; i<NUM_CELLS; i++){
            if (i == index){
                continue;
            }

            conflictedCell = false;

            if (this.mCells[i].getValue() > 0 &&
                    this.mCells[i].getValue() == compareCell.getValue()){
                if (SudokuHelper.calculateColumn(index, NUM_COLS) ==
                    SudokuHelper.calculateColumn(i, NUM_COLS)){
                        conflictedCell = true;
                }

                if (SudokuHelper.calculateRow(index, NUM_COLS) ==
                    SudokuHelper.calculateRow(i, NUM_COLS)){
                        conflictedCell = true;
                }

                if (SudokuHelper.calculateSquare(index, NUM_COLS) ==
                    SudokuHelper.calculateSquare(i, NUM_COLS)){
                        conflictedCell = true;
                }
            }

            if (conflictedCell){
                setConflict(i, true);
            }

            hasConflicts |= conflictedCell;
        }

        return hasConflicts;
    }

    /**
     * Sets the conflict flag
     */
    private void setConflict(int index, boolean conflicted){
        this.mCells[index].setConflict(conflicted);
    }

    /**
     * Change cell's value.
     */
    private void changeValue(char key){
        if ((this.mMode == SudokuBoardMode.SHOW)||
                (this.mMode != SudokuBoardMode.BUILD &&
                this.mCells[this.mSelectedCellIndex].getGenerated())){
            return;
        }
        
        this.mCells[this.mSelectedCellIndex].setValue(Integer.parseInt(String.valueOf(key)));
        if (this.mMode == SudokuBoardMode.BUILD){
            this.mCells[this.mSelectedCellIndex].setGenerated(true);
        }

        if (!checkConflicts()){
            if (cleared()){
                playerWin();
            }
            else{
                this.setSuggestedCellIndex(mEngine.getSuggestion(this));
            }
        }
    }

    /**
     * Clear cell's value;
     */
    private void clearValue(){
        if ((this.mMode == SudokuBoardMode.SHOW) ||
                (this.mMode != SudokuBoardMode.BUILD &&
                this.mCells[this.mSelectedCellIndex].getGenerated())){
            return;
        }
        this.mCells[this.mSelectedCellIndex].setValue(0);
        if (this.mMode == SudokuBoardMode.BUILD){
            this.mCells[this.mSelectedCellIndex].setGenerated(false);
        }

        checkConflicts();
    }

    /**
     * Moves the cursor up.
     */
    private void moveUp(){
        int row = SudokuHelper.calculateRow(this.mSelectedCellIndex, NUM_COLS);
        int col = SudokuHelper.calculateColumn(this.mSelectedCellIndex, NUM_COLS);

        if (row <= 0){
            this.setSelectedCellIndex( (NUM_COLS - 1) * NUM_COLS + col );
        }
        else {
            this.setSelectedCellIndex( (row - 1) * NUM_COLS + col );
        }
    }

    /**
     * Moves the cursor down.
     */
    private void moveDown(){
        int row = SudokuHelper.calculateRow(this.mSelectedCellIndex, NUM_COLS);
        int col = SudokuHelper.calculateColumn(this.mSelectedCellIndex, NUM_COLS);

        if (row >= NUM_COLS - 1) {
            this.setSelectedCellIndex(col);
        }
        else{
            this.setSelectedCellIndex( (row + 1) * NUM_COLS + col );
        }
    }

    /**
     * Moves the cursor left.
     */
    private void moveLeft(){
        int row = SudokuHelper.calculateRow(this.mSelectedCellIndex, NUM_COLS);
        int col = SudokuHelper.calculateColumn(this.mSelectedCellIndex, NUM_COLS);

        if (col <= 0){
            this.setSelectedCellIndex( row * NUM_COLS + (NUM_COLS - 1) );
        }
        else{
            this.setSelectedCellIndex( row * NUM_COLS + col - 1 );
        }
    }

    /**
     * Moves the cursor right.
     */
    private void moveRight(){
        int row = SudokuHelper.calculateRow(this.mSelectedCellIndex, NUM_COLS);
        int col = SudokuHelper.calculateColumn(this.mSelectedCellIndex, NUM_COLS);

        if (col >= NUM_COLS - 1){
            this.setSelectedCellIndex( row * NUM_COLS);
        }
        else{
            this.setSelectedCellIndex( row * NUM_COLS + col + 1 );
        }
    }

    /**
     * Renders a cell
     */
    private void renderCell(Graphics graphics, int index, int x, int y){
        SudokuBoardCell cell = this.mCells[index];
        if (cell != null){
            // Draw border
            setColour(graphics, this.mBorderColour);
            graphics.drawRect(x, y, this.mCellSize, this.mCellSize);

            // Paint background
            if (cell.getConflict()){
                setColour(graphics, this.mConflictedCellColour);
            }
            else {
                if (cell.getGenerated()){
                    setColour(graphics, this.mGeneratedCellColour);
                }
                else{
                    setColour(graphics, this.mCellColour);
                }
            }
            graphics.fillRect(x + 1, y + 1, this.mCellSize - 1, this.mCellSize - 1);

            // Draw suggestion border
            if (cell.getSuggested()){
                setColour(graphics, this.mSuggestedCellColour);
                graphics.drawRect(x+1, y+1, this.mCellSize - 2, this.mCellSize - 2);
            }
            
            // Draw selected border
            if (cell.getSelected()){
                setColour(graphics, this.mSelectedCellColour);
                graphics.drawRect(x + 1, y + 1, this.mCellSize - 2, this.mCellSize - 2);
            }

            //Draw value
            if (cell.getValue() > 0){
                setColour(graphics, this.mFontColour);
                graphics.setFont(this.mFont);
                graphics.drawString(String.valueOf(cell.getValue()),
                        x + (this.mCellSize / 2),
                        y + (this.mCellSize / 4),
                        Graphics.TOP | Graphics.HCENTER);
            }
        }
    }

    /**
     * Sets the current colour.
     */
    private void setColour(Graphics graphics, ColourRGB colour){
        graphics.setColor(colour.getRed(),
            colour.getGreen(),
            colour.getBlue());
    }

    /**
     * Run when player wins.
     */
    private void playerWin(){
        UiHelper.alert(this.mDisplay, "SUDOKU",
                "You win!", AlertType.ALARM);
    }

}
